package registry

import (
	"bytes"
	"encoding/json"
	"fmt"
	"log"
	"math/rand"
	"net/http"
	"net/url"
	"sync"
)

// RegisterService 客户端服务调用这个函数就能进行注册
func RegisterService(r Registration) error {
	// 响应健康检查
	heartbeatURL, err := url.Parse(r.HeartbeatURL)
	if err != nil {
		return err
	}
	http.HandleFunc(heartbeatURL.Path, func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)

		// 如果是生产环境，这里面可能会请求一些其他的信息
		// 如内存、CPU等
	})

	// 转化为 url.URL 类型
	serviceUpdateURL, err := url.Parse(r.ServiceUpdateURL)
	if err != nil {
		return err
	}
	// 为上述 URL 绑定一个 handle 方法
	http.Handle(serviceUpdateURL.Path, &serviceUpdateHandler{})

	// bytes.Buffer 实现了 io.Reader 接口，即 Read(p []byte) (n int, err error) 方法
	buf := new(bytes.Buffer)
	// 对 buf 进行编码
	enc := json.NewEncoder(buf)
	err = enc.Encode(r)
	if err != nil {
		return err
	}

	// 给注册服务器发送一个 POST 请求
	res, err := http.Post(ServicesURL, "application/json", buf)
	if err != nil {
		return err
	}

	if res.StatusCode != http.StatusOK {
		return fmt.Errorf("failed to register service. "+
			"Registry service responded with code %v", res.StatusCode)
	}

	return nil
}

// 服务有变化时，客户端的服务来接收更新的 handler
type serviceUpdateHandler struct{}

func (suh serviceUpdateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}

	// 将 body 解析到 patch 中
	dec := json.NewDecoder(r.Body)
	var p patch
	err := dec.Decode(&p)
	if err != nil {
		log.Println(err)
		w.WriteHeader(http.StatusBadRequest)
		return
	}

	fmt.Printf("Updated received %v\n", p)
	prov.Update(p)
}

// ShutdownService 取消注册服务
func ShutdownService(url string) error {
	// 创建一个 Web 请求
	// 不同于 POST，http 包下没有单独的 DELETE 函数
	// 将 url 转换成 buffer 以实现 io.Reader 接口
	req, err := http.NewRequest(http.MethodDelete, ServicesURL,
		bytes.NewBuffer([]byte(url)))
	if err != nil {
		return err
	}

	// 设置请求头
	req.Header.Add("Content-Type", "text/plain")

	// 发送请求
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}

	// 判断返回的状态码
	if res.StatusCode != http.StatusOK {
		return fmt.Errorf("failed to deregister service. "+
			"Registry service responded with code %v", res.StatusCode)
	}
	return nil
}

// 每个客户端的服务都有自己所依赖的服务
// 它们会向注册中心请求这些服务
// 需要有一个地方存储这些所请求的服务

type providers struct {
	services map[ServiceName][]string // 服务名称与 url 的对应关系
	mutex    *sync.RWMutex
}

// Update 当 provider 接收到变化（即 patch）的时候，我们需要对它进行更新
func (p *providers) Update(pat patch) {
	p.mutex.Lock()
	defer p.mutex.Unlock()

	for _, patchEntry := range pat.Added {
		// 如果服务名还不存在，则新建一个
		if _, ok := p.services[patchEntry.Name]; !ok {
			p.services[patchEntry.Name] = make([]string, 0)
		}

		// 在值的后面附加上 url
		p.services[patchEntry.Name] = append(p.services[patchEntry.Name],
			patchEntry.URL)
	}

	for _, patchEntry := range pat.Removed {
		if providerURLs, ok := p.services[patchEntry.Name]; ok {
			for i := range providerURLs {
				if providerURLs[i] == patchEntry.URL {
					p.services[patchEntry.Name] = append(providerURLs[:i],
						providerURLs[i+1:]...)
				}
			}
		}
	}
}

// 根据服务的名称找到它所依赖的服务的 url
// 返回 []string 也可
func (p providers) get(name ServiceName) (string, error) {
	providers, ok := p.services[name]
	if !ok {
		return "", fmt.Errorf("no providers available for service %v", name)
	}

	// 随机返回一个对应服务的 url
	// 在本项目中，每个服务只有一个 url
	idx := int(rand.Float32() * float32(len(providers)))
	return providers[idx], nil
}

func GetProvider(name ServiceName) (string, error) {
	return prov.get(name)
}

var prov = providers{
	services: make(map[ServiceName][]string),
	mutex:    new(sync.RWMutex),
}
